import { execSync } from 'child_process'
import path from 'path'
import { fileURLToPath } from 'url'

import { DATETIME_TYPE_ENUM } from '../../../src/pages/components/App/components/AddNewPage/enum.js'

import { addKeyValueToLoginRoutesMap } from './addKeyValueToMapInFile.js'
import { generateFileFromTemplate } from './generateFileFromTemplate.js'

/**
 * @typedef {Object} PostParams
 * @property {string} namespace 命名空间
 * @property {string} addPopupType 新增框打开方式
 * @property {string} pageName 页面名称
 * @property {string} fileUrl 文件路径
 * @property {Array} searchFormData 搜索表单数据
 * @property {Array} tableColumns 表格列数据
 * @property {Array} formData 表单数据
 */

const __filename = fileURLToPath(import.meta.url)
/** 当前脚本的文件路径 */
const __dirname = path.dirname(__filename)

/** 页面文件所在的目录 */
const ROUTES_DIR = '../../../src/pages'
/** models文件所在的目录 */
const MODELS_DIR = '../../../src/models'
/** api文件所在的目录 */
const SERVICES_DIR = '../../../src/api'
/** 路由工具文件所在的目录 */
const UTILS_DIR = '../../../src/router'

/** 页面模板文件的路径 */
const ROUTES_INDEX_TEMPLATE_PATH = path.join(__dirname, 'template/pages/index.txt')
/** 编辑模态框模板文件的路径 */
const ROUTES_EDIT_MODAL_TEMPLATE_PATH = path.join(__dirname, 'template/pages/EditModal.txt')
/** 编辑抽屉框模板文件的路径 */
const ROUTES_EDIT_DRAWER_TEMPLATE_PATH = path.join(__dirname, 'template/pages/EditDrawer.txt')
/** models模板文件的路径 */
const MODELS_TEMPLATE_PATH = path.join(__dirname, 'template/models.txt')
/** api模板文件的路径 */
const SERVICES_TEMPLATE_PATH = path.join(__dirname, 'template/api.txt')

/** 登录路由映射文件的路径 */
const LOGIN_ROUTES_MAP_PATH = path.join(__dirname, `${UTILS_DIR}/index.js`)
/** 模型和组件映射文件的路径 */
const MODELS_AND_COMPONENT_MAP_PATH = path.join(__dirname, `${UTILS_DIR}/modelsAndComponent.js`)

/**
 * 将字符串的首字母大写
 * @param {string} str - 要处理的字符串
 * @returns {string} - 首字母大写后的字符串
 */
function capitalizeFirstLetter(str) {
  return str.charAt(0).toUpperCase() + str.slice(1)
}

/**
 * 使用 ESLint 格式化指定文件列表
 * @param {string[]} filePathList - 文件路径列表
 */
const eslintFix = filePathList => {
  try {
    // 构建 ESLint 格式化命令
    const command = `pnpm eslint --fix ${filePathList.join(' ')}`
    // 执行命令并转换输出为字符串
    execSync(command).toString()
    console.log(`格式化文件成功:\n${filePathList.join('\n')}`)
  } catch (error) {
    console.error(`执行命令时出错: ${error.message}`)
  }
}

/**
 * 构建公共的替换模板的键值对
 * @param {PostParams} postParams - 包含页面信息的参数对象
 * @returns {Object} 替换模板的键值对
 */
const buildReplacements = postParams => {
  const { namespace, pageName, fileUrl } = postParams
  return {
    namespace,
    namespaceCFL: capitalizeFirstLetter(namespace),
    pageName,
    fileUrl,
  }
}

/**
 * 构建搜索表单数据的字符串
 * @param {Array} searchFormData - 搜索表单数据
 * @returns {string} 格式化后的搜索表单数据字符串
 */
const buildSearchFormData = searchFormData => {
  return searchFormData
    .map(({ field, label, type }) =>
      JSON.stringify({
        field: field,
        label: label,
        component: {
          name:
            type === DATETIME_TYPE_ENUM.DATE
              ? 'DatePicker'
              : type === DATETIME_TYPE_ENUM.DATE_RANGE
                ? 'RangePicker'
                : 'Input',
        },
      }),
    )
    .join(',')
}

/**
 * 构建表格列数据的字符串
 * @param {Array} tableColumns - 表格列数据
 * @returns {string} 格式化后的表格列数据字符串
 */
const buildTableColumns = tableColumns => {
  return tableColumns
    .map(({ field, label, type }) =>
      JSON.stringify({
        dataIndex: field,
        title: label,
        isDate: type === DATETIME_TYPE_ENUM.DATE || undefined,
        isDateTime: type === DATETIME_TYPE_ENUM.DATETIME || undefined,
      }),
    )
    .join(',')
}

/**
 * 构建表单数据的字符串
 * @param {Array} formData - 表单数据
 * @returns {string} 格式化后的表单数据字符串
 */
const buildFormData = formData => {
  return formData
    .map(({ field, label, type, required }) =>
      JSON.stringify({
        field: field,
        label: label,
        formItemProps: required
          ? {
              rules: [{ required: true }],
            }
          : undefined,
        component: {
          name:
            type === DATETIME_TYPE_ENUM.DATE
              ? 'DatePicker'
              : type === DATETIME_TYPE_ENUM.DATETIME
                ? 'TimePicker'
                : 'Input',
        },
      }),
    )
    .join(',')
}

/**
 * 创建路由文件
 * @param {PostParams} postParams - 包含页面信息的参数对象
 */
const createRoute = async postParams => {
  const { searchFormData, tableColumns, formData, addPopupType, fileUrl, namespace } = postParams
  const replacements = buildReplacements(postParams)
  replacements.searchFormData = buildSearchFormData(searchFormData)
  replacements.tableColumns = buildTableColumns(tableColumns)
  replacements.formData = buildFormData(formData)
  replacements.editPopUp = addPopupType === 'modal' ? 'EditModal' : 'EditDrawer'

  // 创建列表页面
  const listOutputPath = path.join(__dirname, `${ROUTES_DIR}/${fileUrl}/${namespace}/index.jsx`)
  await generateFileFromTemplate(ROUTES_INDEX_TEMPLATE_PATH, listOutputPath, replacements)

  // 创建新增弹出框
  const modalOutputPath = path.join(
    __dirname,
    `${ROUTES_DIR}/${fileUrl}/${namespace}/components/${addPopupType === 'modal' ? 'EditModal' : 'EditDrawer'}/index.jsx`,
  )
  await generateFileFromTemplate(
    addPopupType === 'modal' ? ROUTES_EDIT_MODAL_TEMPLATE_PATH : ROUTES_EDIT_DRAWER_TEMPLATE_PATH,
    modalOutputPath,
    replacements,
  )
}

/**
 * 创建模型文件
 * @param {PostParams} postParams - 包含页面信息的参数对象
 */
const createModel = async postParams => {
  const replacements = buildReplacements(postParams)
  const outputPath = path.join(__dirname, `${MODELS_DIR}/${postParams.fileUrl}/${postParams.namespace}/index.js`)
  await generateFileFromTemplate(MODELS_TEMPLATE_PATH, outputPath, replacements)
}

/**
 * 创建服务文件
 * @param {PostParams} postParams - 包含页面信息的参数对象
 */
const createService = async postParams => {
  const replacements = buildReplacements(postParams)
  const outputPath = path.join(__dirname, `${SERVICES_DIR}/${postParams.fileUrl}/${postParams.namespace}/index.js`)
  await generateFileFromTemplate(SERVICES_TEMPLATE_PATH, outputPath, replacements)
}

/**
 * 添加路由信息到映射文件
 * @param {PostParams} postParams - 包含页面信息的参数对象
 */
const addRoute = async postParams => {
  const { namespace, pageName, fileUrl } = postParams
  // 添加到 loginRoutesMap
  const loginRoutesKeyValue = {
    [namespace]: {
      pageKey: namespace,
      name: pageName,
    },
  }
  await addKeyValueToLoginRoutesMap({
    inputFilePath: LOGIN_ROUTES_MAP_PATH,
    outputFilePath: LOGIN_ROUTES_MAP_PATH,
    newKeyValue: loginRoutesKeyValue,
    mapName: 'loginRoutesMap',
  })

  // 添加到 modelsMap
  const modelsMapKeyValue = {
    [namespace]: eval(`() => [import('@/models/${fileUrl}/${namespace}')]`),
  }
  await addKeyValueToLoginRoutesMap({
    inputFilePath: MODELS_AND_COMPONENT_MAP_PATH,
    outputFilePath: MODELS_AND_COMPONENT_MAP_PATH,
    newKeyValue: modelsMapKeyValue,
    mapName: 'modelsMap',
  })

  // 添加到 componentMap
  const componentMapKeyValue = {
    [namespace]: eval(`() => import('@/pages/${fileUrl}/${namespace}')`),
  }
  await addKeyValueToLoginRoutesMap({
    inputFilePath: MODELS_AND_COMPONENT_MAP_PATH,
    outputFilePath: MODELS_AND_COMPONENT_MAP_PATH,
    newKeyValue: componentMapKeyValue,
    mapName: 'componentMap',
  })
}

/**
 * 创建页面，包括路由、模型、服务文件，并添加路由信息到映射文件，最后格式化文件
 * @param {PostParams} postParams - 包含页面信息的参数对象
 */
export const createPage = async postParams => {
  const { namespace, fileUrl, addPopupType } = postParams

  // 创建路由、模型、服务文件
  await createRoute(postParams)
  await createModel(postParams)
  await createService(postParams)

  // 添加路由信息到映射文件
  await addRoute(postParams)

  // 收集需要格式化的文件路径
  const outputPaths = [
    path.join(__dirname, `${ROUTES_DIR}/${fileUrl}/${namespace}/index.jsx`),
    path.join(
      __dirname,
      `${ROUTES_DIR}/${fileUrl}/${namespace}/components/${addPopupType === 'modal' ? 'EditModal' : 'EditDrawer'}/index.jsx`,
    ),
    path.join(__dirname, `${MODELS_DIR}/${fileUrl}/${namespace}/index.js`),
    path.join(__dirname, `${SERVICES_DIR}/${fileUrl}/${namespace}/index.js`),
    path.join(__dirname, `${UTILS_DIR}/index.js`),
    path.join(__dirname, `${UTILS_DIR}/modelsAndComponent.js`),
  ]

  // 使用 ESLint 格式化文件
  eslintFix(outputPaths)
}
